#include<iostream>
#include<string>
#include<unistd.h>
#include<pthread.h>

using namespace std;

//细节：
//1.凡是访问同一个临界资源的线程，都要进行加锁保护，必须加同一把锁。
//2.每一个线程访问临界区之前，得加锁，加锁的本质是给临界区加锁。加锁的粒度要细一些,避免影响串行时间变长
//3.线程访问临界区的时候，需要先加锁->所有线程必须要先看到同一把锁
//->所以锁本身就是一个临界资源->锁如何保证自己的安全？
//加锁解锁本身就是原子的
//4.临界区可以是一行代码，一批代码，a.加锁后线程可能被切换吗？当然可能，不要特殊化加锁和解锁，还有临界区代码。
//b.切换会有影响吗？不会，因为在这个线程被切走的期间，任何线程都没有办法进入临界区，因为它无法申请到锁!
//”在这个切走的这个期间锁被我拿走了“，只有等”我“回来并且将这个临界资源访问完了，并解锁了，其它线程才能
//才能访问这块临界资源
//这也正是体现互斥带来的串行化表现，站在其它线程的角度，对其它线程有意义的状态就是：锁被我申请(持有锁)，
//锁被我释放了(不持有锁)，原子性也就体现在这里。

//临界资源
int tickets = 1000;//全局变量，共享对象

//如果将锁定义成全局，则可以采用宏函数的方式对锁进行初始化
//pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

class TData
{
public:
    TData(const string& name,pthread_mutex_t* mutex)
        :_name(name)
        ,_pmutex(mutex)
    {}
    ~TData()
    {}
public:
    string _name;//线程名称
    pthread_mutex_t* _pmutex;//锁指针
};

void* threadRoutine(void* args)
{
    TData* td = static_cast<TData*>(args);

    while(true)
    {
        //对临界区进行加锁
        pthread_mutex_lock(td->_pmutex);
        if(tickets > 0)
        {
            //在睡眠的这2000us期间，就会导致多线程争先访问临界区
            usleep(2000);
            cout << td->_name << "get a ticket: " << tickets-- << endl;//临界区
            pthread_mutex_unlock(td->_pmutex);
        }
        else
        {
            pthread_mutex_unlock(td->_pmutex);
            break;
        }
        //抢完一张票之后，还有后序动作
        //usleep(13);
    }
    return nullptr;
}

int main()
{
    pthread_mutex_t mutex;//定义锁
    pthread_mutex_init(&mutex,nullptr);//初始化锁

    pthread_t tids[4];
    int n = sizeof(tids)/sizeof(tids[0]);
    for(int i=0;i<n;i++)
    {
        char name[64];
        snprintf(name,64,"thread-%d",i+1);
        TData* td = new TData(name,&mutex);
        //创建线程
        pthread_create(tids+i,nullptr,threadRoutine,td);
    }
    for(int i=0;i<n;i++)
    {
        //等待线程
        pthread_join(tids[i],nullptr);
    }
    //销毁锁
    pthread_mutex_destroy(&mutex);
    return 0;
}
